import {
    PluginSettingTab,
    Setting,
    App,
    ButtonComponent,
    Modal,
    TextComponent,
    Notice,
    setIcon,
    TFile,
    Platform,
    TextAreaComponent
} from "obsidian";
import {
    Admonition,
    AdmonitionIconDefinition,
    AdmonitionIconName,
    AdmonitionIconType
} from "./@types";

import {
    ADD_COMMAND_NAME,
    REMOVE_COMMAND_NAME,
    SPIN_ICON_NAME,
    WARNING_ICON_NAME
} from "./util";

import { IconSuggestionModal } from "./modal";

//@ts-expect-error
import CONTENT from "../publish/publish.admonition.txt";

import { t } from "src/lang/helpers";
import ObsidianAdmonition from "./main";
import { confirmWithModal } from "./modal/confirm";
import { DownloadableIconPack, DownloadableIcons } from "./icons/packs";
import { AdmonitionValidator } from "./util/validator";
import Export from "./modal/export";

/** Taken from https://stackoverflow.com/questions/34849001/check-if-css-selector-is-valid/42149818 */
const isSelectorValid = ((dummyElement) => (selector: string) => {
    try {
        dummyElement.querySelector(selector);
    } catch {
        return false;
    }
    return true;
})(document.createDocumentFragment());

export default class AdmonitionSetting extends PluginSettingTab {
    additionalEl: HTMLDivElement;
    notice: Notice;
    constructor(app: App, public plugin: ObsidianAdmonition) {
        super(app, plugin);
    }
    async display(): Promise<void> {
        this.containerEl.empty();
        this.containerEl.addClass("admonition-settings");
        this.containerEl.createEl("h2", { text: t("Admonition Settings") });

        const admonitionEl = this.containerEl.createDiv(
            "admonitions-nested-settings"
        );
        if (!Platform.isMobile) {
            new Setting(admonitionEl)
                .setName("Export Custom Types as CSS")
                .setDesc("Export a CSS snippet for custom callout types.")
                .addButton((b) =>
                    b
                        .setIcon("download")
                        .onClick(() => {
                            const sheet = [
                                `/* This snippet was auto-generated by the Admonitions plugin */\n\n`
                            ];
                            const file = new Blob(
                                [
                                    this.plugin.calloutManager.generateCssString()
                                ],
                                {
                                    type: "text/css"
                                }
                            );
                            createEl("a", {
                                attr: {
                                    download: "custom_callouts.css",
                                    href: URL.createObjectURL(file)
                                }
                            }).click();
                        })
                        .setDisabled(
                            !Object.keys(this.plugin.data.userAdmonitions)
                                .length
                        )
                );
        }

        new Setting(admonitionEl)
            .setName("Export Custom Types as JSON")
            .setDesc(
                "Choose custom types to export as a JSON file that you can then share with other users."
            )
            .addButton((b) =>
                b
                    .setButtonText("Download All")
                    .setCta()
                    .onClick(() => {
                        const admonitions = Object.values(
                            this.plugin.data.userAdmonitions
                        );
                        this.download(admonitions);
                    })
            )
            .addButton((b) =>
                b.setButtonText("Select & Download").onClick(() => {
                    const modal = new Export(this.plugin);
                    modal.onClose = () => {
                        if (!modal.export) return;
                        const admonitions = Object.values(
                            this.plugin.data.userAdmonitions
                        );
                        this.download(
                            admonitions.filter((a) =>
                                modal.selectedAdmonitions.includes(a.type)
                            )
                        );
                    };
                    modal.open();
                })
            );

        new Setting(admonitionEl)
            .setName("Use CSS Snippet for Custom Callouts")
            .setDesc(
                "Instead of managing it internally, Admonitions will maintain a CSS snippet to enable your custom types for callouts."
            )
            .addToggle((t) =>
                t.setValue(this.plugin.data.useSnippet).onChange((v) => {
                    this.plugin.data.useSnippet = v;
                    this.plugin.saveSettings();
                    this.plugin.calloutManager.setUseSnippet();
                })
            );

        new Setting(admonitionEl)
            .setName(t("Add New"))
            .setDesc(
                "Add a new Admonition type. All custom Admonitions will also be usable as callouts."
            )
            .addButton((button: ButtonComponent): ButtonComponent => {
                let b = button
                    .setTooltip(t("Add Additional"))
                    .setButtonText("+")
                    .onClick(async () => {
                        let modal = new SettingsModal(this.plugin);

                        modal.onClose = async () => {
                            if (modal.saved) {
                                const admonition = {
                                    type: modal.type,
                                    color: modal.color,
                                    icon: modal.icon,
                                    command: false,
                                    title: modal.title,
                                    injectColor: modal.injectColor,
                                    noTitle: modal.noTitle,
                                    copy: modal.copy
                                };
                                this.plugin.addAdmonition(admonition);

                                this.plugin.calloutManager.addAdmonition(
                                    admonition
                                );
                                this.display();
                            }
                        };

                        modal.open();
                    });

                return b;
            });
        new Setting(admonitionEl)
            .setName("Import Admonition(s)")
            .setDesc("Import admonitions from a JSON definition.")
            .addButton((b) => {
                const input = createEl("input", {
                    attr: {
                        type: "file",
                        name: "merge",
                        accept: ".json",
                        multiple: true,
                        style: "display: none;"
                    }
                });
                input.onchange = async () => {
                    const { files } = input;

                    if (!files.length) return;
                    try {
                        const data: Admonition[][] | Admonition[] = [];
                        for (let file of Array.from(files)) {
                            data.push(JSON.parse(await file.text()));
                        }
                        for (const item of data.flat()) {
                            if (typeof item != "object") continue;

                            if (!item.icon) {
                                item.icon = {
                                    name: "pencil-alt",
                                    type: "font-awesome"
                                };
                            }
                            const valid = AdmonitionValidator.validateImport(
                                this.plugin,
                                item
                            );
                            if (valid.success == false) {
                                new Notice(
                                    createFragment((e) => {
                                        e.createSpan({
                                            text: `There was an issue importing the ${item.type} admonition:`
                                        });
                                        e.createEl("br");
                                        e.createSpan({ text: valid.message });
                                    })
                                );
                                continue;
                            }
                            if (valid.messages?.length) {
                                new Notice(
                                    createFragment((e) => {
                                        e.createSpan({
                                            text: `There was an issue importing the ${item.type} admonition:`
                                        });
                                        for (const message of valid.messages) {
                                            e.createEl("br");
                                            e.createSpan({
                                                text: message
                                            });
                                        }
                                    })
                                );
                            }
                            await this.plugin.addAdmonition(item);
                        }
                        this.display();
                    } catch (e) {
                        new Notice(
                            `There was an error while importing the admonition${
                                files.length == 1 ? "" : "s"
                            }.`
                        );
                        console.error(e);
                    }

                    input.value = null;
                };
                b.setButtonText("Choose Files");
                b.buttonEl.appendChild(input);
                b.onClick(() => input.click());
            })
            .addExtraButton((b) =>
                b.setIcon("info").onClick(() => {
                    const modal = new Modal(this.plugin.app);
                    modal.onOpen = () => {
                        modal.contentEl.createSpan({
                            text: "Import one or more admonition definitions as a JSON array. An admonition definition should look as follows at minimum:"
                        });
                        modal.contentEl.createEl("br");
                        const textarea = new TextAreaComponent(
                            modal.contentEl.createDiv()
                        )
                            .setDisabled(true)
                            .setValue(
                                JSON.stringify(
                                    {
                                        type: "embed-affliction",
                                        color: "149, 214, 148",
                                        icon: {
                                            name: "head-side-cough",
                                            type: "font-awesome"
                                        }
                                    },
                                    null,
                                    4
                                )
                            );
                        textarea.inputEl.setAttribute(
                            "style",
                            `height: ${textarea.inputEl.scrollHeight}px; resize: none;`
                        );
                        modal.contentEl.createEl("br");
                        modal.contentEl.createSpan({
                            text: "See the plugin ReadMe for more information."
                        });
                    };
                    modal.open();
                })
            );
        this.additionalEl = admonitionEl.createDiv("additional");
        this.buildTypes();

        this.buildAdmonitions(
            this.containerEl.createEl("details", {
                cls: "admonitions-nested-settings",
                attr: {
                    ...(this.plugin.data.open.admonitions ? { open: true } : {})
                }
            })
        );
        this.buildIcons(
            this.containerEl.createEl("details", {
                cls: "admonitions-nested-settings",
                attr: {
                    ...(this.plugin.data.open.icons ? { open: true } : {})
                }
            })
        );
        this.buildOtherSyntaxes(
            this.containerEl.createEl("details", {
                cls: "admonitions-nested-settings",
                attr: {
                    ...(this.plugin.data.open.other ? { open: true } : {})
                }
            })
        );
        this.buildAdvanced(
            this.containerEl.createEl("details", {
                cls: "admonitions-nested-settings",
                attr: {
                    ...(this.plugin.data.open.advanced ? { open: true } : {})
                }
            })
        );

        const div = this.containerEl.createDiv("coffee");
        div.createEl("a", {
            href: "https://www.buymeacoffee.com/valentine195"
        }).createEl("img", {
            attr: {
                src: "https://img.buymeacoffee.com/button-api/?text=Buy me a coffee&emoji=☕&slug=valentine195&button_colour=e3e7ef&font_colour=262626&font_family=Inter&outline_colour=262626&coffee_colour=ff0000"
            }
        });
    }
    download(admonitions: Admonition[]) {
        if (!admonitions.length) {
            new Notice("At least one admonition must be chosen to export.");
            return;
        }
        const link = createEl("a");
        const file = new Blob([JSON.stringify(admonitions)], {
            type: "json"
        });
        const url = URL.createObjectURL(file);
        link.href = url;
        link.download = `admonitions.json`;
        link.click();
        URL.revokeObjectURL(url);
    }
    buildAdmonitions(containerEl: HTMLDetailsElement) {
        containerEl.empty();
        containerEl.ontoggle = () => {
            this.plugin.data.open.admonitions = containerEl.open;
            this.plugin.saveSettings();
        };
        const summary = containerEl.createEl("summary");
        new Setting(summary).setHeading().setName("Admonitions & Callouts");
        summary.createDiv("collapser").createDiv("handle");

        new Setting(containerEl)
            .setName("Add Drop Shadow")
            .setDesc("A drop shadow will be added to admonitions.")
            .addToggle((t) => {
                t.setValue(this.plugin.data.dropShadow).onChange(async (v) => {
                    this.plugin.data.dropShadow = v;
                    this.display();
                    await this.plugin.saveSettings();
                });
            });
        new Setting(containerEl)
            .setName(t("Collapsible by Default"))
            .setDesc(
                createFragment((e) => {
                    e.createSpan({
                        text: "All admonitions & callouts will be collapsible by default. Use "
                    });
                    e.createEl("code", {
                        text: "collapse: none"
                    });
                    e.createSpan({
                        text: t(" to prevent.")
                    });
                })
            )
            .addToggle((t) => {
                t.setValue(this.plugin.data.autoCollapse).onChange(
                    async (v) => {
                        this.plugin.data.autoCollapse = v;
                        this.display();
                        await this.plugin.saveSettings();
                    }
                );
            });

        if (this.plugin.data.autoCollapse) {
            new Setting(containerEl)
                .setName(t("Default Collapse Type"))
                .setDesc(
                    "Collapsible admonitions & callouts will be either opened or closed."
                )
                .addDropdown((d) => {
                    d.addOption("open", "open");
                    d.addOption("closed", "closed");
                    d.setValue(this.plugin.data.defaultCollapseType);
                    d.onChange(async (v: "open" | "closed") => {
                        this.plugin.data.defaultCollapseType = v;
                        await this.plugin.saveSettings();
                    });
                });
        }
        new Setting(containerEl)
            .setName(t("Add Copy Button"))
            .setDesc("Add a 'copy content' button to admonitions & callouts.")
            .addToggle((t) => {
                t.setValue(this.plugin.data.copyButton);
                t.onChange(async (v) => {
                    this.plugin.data.copyButton = v;

                    if (!v) {
                        document
                            .querySelectorAll(".admonition-content-copy")
                            .forEach((el) => {
                                el.detach();
                            });
                    }

                    await this.plugin.saveSettings();
                });
            });
        new Setting(containerEl)
            .setName(t("Parse Titles as Markdown"))
            .setDesc(t("Admonition Titles will be rendered as markdown."))
            .addToggle((t) => {
                t.setValue(this.plugin.data.parseTitles);
                t.onChange(async (v) => {
                    this.plugin.data.parseTitles = v;

                    await this.plugin.saveSettings();
                });
            });

        new Setting(containerEl)
            .setName("Set Admonition Colors")
            .setDesc(
                "Disable this setting to turn off admonition coloring by default. Can be overridden in the admonition definition."
            )
            .addToggle((t) =>
                t
                    .setValue(this.plugin.data.injectColor)
                    .setTooltip(
                        `${
                            this.plugin.data.injectColor ? "Disable" : "Enable"
                        } Admonition Color`
                    )
                    .onChange(async (v) => {
                        this.plugin.data.injectColor = v;

                        await this.plugin.saveSettings();

                        await this.buildTypes();
                    })
            );
        new Setting(containerEl)
            .setName("Hide Empty Admonitions")
            .setDesc(
                "Any admonition that does not have content inside it will be hidden."
            )
            .addToggle((t) =>
                t.setValue(this.plugin.data.hideEmpty).onChange(async (v) => {
                    this.plugin.data.hideEmpty = v;

                    await this.plugin.saveSettings();

                    await this.buildTypes();
                })
            );
    }

    buildIcons(containerEl: HTMLDetailsElement) {
        containerEl.empty();
        containerEl.ontoggle = () => {
            this.plugin.data.open.icons = containerEl.open;
            this.plugin.saveSettings();
        };
        const summary = containerEl.createEl("summary");
        new Setting(summary).setHeading().setName("Icon Packs");
        summary.createDiv("collapser").createDiv("handle");

        new Setting(containerEl)
            .setName("Use Font Awesome Icons")
            .setDesc(
                "Font Awesome Free icons will be available in the item picker. Existing Admonitions defined using Font Awesome icons will continue to work."
            )
            .addToggle((t) => {
                t.setValue(this.plugin.data.useFontAwesome).onChange((v) => {
                    this.plugin.data.useFontAwesome = v;
                    this.plugin.iconManager.setIconDefinitions();
                    this.plugin.saveSettings();
                });
            });

        let selected: DownloadableIconPack;
        const possibilities = Object.entries(DownloadableIcons).filter(
            ([icon]) =>
                !this.plugin.data.icons.includes(icon as DownloadableIconPack)
        );
        new Setting(containerEl)
            .setName("Load Additional Icons")
            .setDesc(
                "Load an additional icon pack. This requires an internet connection."
            )
            .addDropdown((d) => {
                if (!possibilities.length) {
                    d.setDisabled(true);
                    return;
                }
                for (const [icon, display] of possibilities) {
                    d.addOption(icon, display);
                }
                d.onChange((v: DownloadableIconPack) => (selected = v));
                selected = d.getValue() as DownloadableIconPack;
            })
            .addExtraButton((b) => {
                b.setIcon("plus-with-circle")
                    .setTooltip("Load")
                    .onClick(async () => {
                        if (!selected || !selected.length) return;

                        await this.plugin.iconManager.downloadIcon(selected);
                        this.buildIcons(containerEl);
                    });
                if (!possibilities.length) b.setDisabled(true);
            });

        const iconsEl = containerEl.createDiv("admonitions-nested-settings");
        new Setting(iconsEl);
        for (const icon of this.plugin.data.icons) {
            new Setting(iconsEl)
                .setName(DownloadableIcons[icon])
                .addExtraButton((b) => {
                    b.setIcon("reset")
                        .setTooltip("Redownload")
                        .onClick(async () => {
                            await this.plugin.iconManager.removeIcon(icon);
                            await this.plugin.iconManager.downloadIcon(icon);
                            this.buildIcons(containerEl);
                        });
                })
                .addExtraButton((b) => {
                    b.setIcon("trash").onClick(async () => {
                        if (
                            Object.values(
                                this.plugin.data.userAdmonitions
                            ).find((admonition) => admonition.icon.type == icon)
                        ) {
                            if (
                                !(await confirmWithModal(
                                    this.plugin.app,
                                    "You have Admonitions using icons from this pack. Are you sure you want to remove it?"
                                ))
                            )
                                return;
                        }

                        await this.plugin.iconManager.removeIcon(icon);

                        this.buildIcons(containerEl);
                    });
                });
        }
    }

    buildOtherSyntaxes(containerEl: HTMLDetailsElement) {
        containerEl.empty();
        containerEl.ontoggle = () => {
            this.plugin.data.open.other = containerEl.open;
            this.plugin.saveSettings();
        };
        const summary = containerEl.createEl("summary");
        new Setting(summary).setHeading().setName("Additional Syntaxes");
        summary.createDiv("collapser").createDiv("handle");

        containerEl.createEl("p", {
            text: "Obsidian 0.14 has introduced Callout boxes to its core functionality using the same syntax as the Microsoft Document callouts.",

            cls: "setting-item"
        });
        containerEl.createEl("p", {
            text: "This has rendered the Microsoft Document syntax for Admonitions obsolete, but Admonitions can still be used to create and manage your custom callout types.",

            cls: "setting-item"
        });
        containerEl.createEl("p", {
            text: "Your existing code block Admonitions will always work!",

            cls: "setting-item"
        });

        if (!this.plugin.data.msDocConverted) {
            new Setting(containerEl)
                .setName("Convert MSDoc Admonitions to Callouts")
                .setDesc(
                    createFragment((e) => {
                        const text = e.createDiv("admonition-convert");
                        setIcon(text.createSpan(), WARNING_ICON_NAME);
                        text.createSpan({
                            text: "This "
                        });
                        text.createEl("strong", { text: "will" });
                        text.createSpan({
                            text: " modify notes. Use at your own risk and please make backups."
                        });
                        e.createEl("p", {
                            text: "With large vaults, this could take awhile!"
                        });
                    })
                )
                .addButton((b) =>
                    b
                        .setButtonText("Convert")
                        .setCta()
                        .onClick(() => {
                            this.queue =
                                this.plugin.app.vault.getMarkdownFiles();
                            this.notice = new Notice(
                                createFragment((e) => {
                                    const container =
                                        e.createDiv("admonition-convert");
                                    container.createSpan({
                                        text: "Converting MS-doc admonitions..."
                                    });
                                    setIcon(
                                        container.createSpan(
                                            "admonition-convert-icon"
                                        ),
                                        SPIN_ICON_NAME
                                    );
                                }),
                                0
                            );
                            this.checkAndReplace();
                        })
                );
        }
        new Setting(containerEl)
            .setName("Convert Codeblock Admonitions to Callouts")
            .setDesc(
                createFragment((e) => {
                    const text = e.createDiv("admonition-convert");
                    setIcon(text.createSpan(), WARNING_ICON_NAME);
                    text.createSpan({
                        text: "This "
                    });
                    text.createEl("strong", { text: "will" });
                    text.createSpan({
                        text: " modify notes. Use at your own risk and please make backups."
                    });
                    e.createEl("p", {
                        text: "With large vaults, this could take awhile!"
                    });
                })
            )
            .addButton((b) =>
                b
                    .setButtonText("Convert")
                    .setCta()
                    .onClick(() => {
                        this.queue = this.plugin.app.vault.getMarkdownFiles();
                        /* this.queue = [
                            this.plugin.app.vault.getAbstractFileByPath(
                                "99 Plugin Testing/admonition/Admonition Codeblock.md"
                            ) as TFile
                        ]; */
                        this.notice = new Notice(
                            createFragment((e) => {
                                const container =
                                    e.createDiv("admonition-convert");
                                container.createSpan({
                                    text: "Converting Codeblock admonitions..."
                                });
                                setIcon(
                                    container.createSpan(
                                        "admonition-convert-icon"
                                    ),
                                    SPIN_ICON_NAME
                                );
                            }),
                            0
                        );
                        this.converted = 0;
                        this.checkAndReplaceCodeBlocks();
                    })
            );
    }
    queue: TFile[] = [];
    converted = 0;
    async checkAndReplace() {
        if (!this.queue.length) {
            if (this.converted) {
                this.notice.setMessage(
                    `${this.converted} MS-doc Admonitions converted!`
                );
            } else {
                this.notice.setMessage(
                    "No MS-doc Admonitions found to convert."
                );
            }
            this.plugin.data.msDocConverted = true;
            this.plugin.saveSettings().then(() => this.display());

            setTimeout(() => {
                this.notice.hide();
                this.notice = undefined;
            }, 2000);
            return;
        }
        setTimeout(async () => {
            const file = this.queue.shift();
            const contents = await this.app.vault.read(file);
            if (/> \[!([^ :]+)(?::[ ]?(.+))\](x|\+|\-)?/.test(contents)) {
                this.converted++;
                await this.plugin.app.vault.modify(
                    file,
                    contents.replace(
                        /> \[!([^ :]+)(?::[ ]?(.+))\](x|\+|\-)?/g,
                        `> [!$1]$3 $2`
                    )
                );
            }
            this.checkAndReplace();
        });
    }
    async checkAndReplaceCodeBlocks() {
        if (!this.queue.length) {
            if (this.converted) {
                this.notice.setMessage(
                    `${this.converted} Codeblock Admonitions converted!`
                );
            } else {
                this.notice.setMessage(
                    "No Codeblock Admonitions found to convert."
                );
            }
            this.display();
            setTimeout(() => {
                this.notice.hide();
                this.notice = undefined;
            }, 2000);
            return;
        }
        setTimeout(async () => {
            const file = this.queue.shift();
            let contents = await this.app.vault.read(file);

            if (/^(`{3,})ad-(\w+)([\s\S]*?)?\n^\1/m.test(contents)) {
                contents = this.replaceCodeBlockInPlace(contents);
                this.app.vault.modify(file, contents);
            }
            this.checkAndReplaceCodeBlocks();
        });
    }
    replaceCodeBlockInPlace(contents: string): string {
        const admonitions =
            contents.match(/^(`{3,})ad-(\w+)([\s\S]*?)?\n^\1/gm) ?? [];

        for (const admonition of admonitions) {
            let [, type] = admonition.match(/^`{3,}ad-(\w+)/),
                title = "",
                collapse = "";
            if (!type) continue;
            let content = [];

            let mine = true;
            for (const line of admonition.split("\n").slice(1, -1)) {
                if (mine) {
                    if (/^title:/.test(line)) {
                        title =
                            line.match(/^title:(.*)/)?.[1].trim() ??
                            type[0].toUpperCase() + type.slice(1).toLowerCase();
                        continue;
                    }
                    if (/^collapse:/.test(line)) {
                        const state =
                            line.match(/^collapse:\s?(.*)/)?.[1].trim() ??
                            "open";
                        collapse = state == "open" ? "+" : "-";
                        continue;
                    }
                    if (!/^(title|collapse|color|icon):/.test(line)) {
                        mine = false;
                    }
                }
                content.push(line);
            }

            let parsed = content.join("\n");
            if (/^(`{3,})ad-(\w+)([\s\S]*?)?\n^\1/m.test(parsed)) {
                parsed = this.replaceCodeBlockInPlace(parsed);
            }
            contents = contents.replace(
                admonition,
                `> [!${type}]${collapse}${
                    title.length ? " " : ""
                }${title}\n> ${parsed.split("\n").join("\n> ")}`
            );

            this.converted++;
        }
        return contents;
    }
    buildAdvanced(containerEl: HTMLDetailsElement) {
        containerEl.empty();
        containerEl.ontoggle = () => {
            this.plugin.data.open.advanced = containerEl.open;
            this.plugin.saveSettings();
        };
        const summary = containerEl.createEl("summary");
        new Setting(summary).setHeading().setName("Advanced Settings");
        summary.createDiv("collapser").createDiv("handle");

        new Setting(containerEl)
            .setName(t("Markdown Syntax Highlighting"))
            .setDesc(
                t(
                    "Use Obsidian's markdown syntax highlighter in admonition code blocks. This setting is experimental and could cause errors."
                )
            )
            .addToggle((t) => {
                t.setValue(this.plugin.data.syntaxHighlight);
                t.onChange(async (v) => {
                    this.plugin.data.syntaxHighlight = v;
                    if (v) {
                        this.plugin.turnOnSyntaxHighlighting();
                    } else {
                        this.plugin.turnOffSyntaxHighlighting();
                    }
                    await this.plugin.saveSettings();
                });
            });

        // new Setting(containerEl)
        //     .setName("Generate JS for Publish")
        //     .setDesc(
        //         createFragment((f) => {
        //             f.createSpan({
        //                 text: "Generate a javascript file to place in your "
        //             });
        //             f.createEl("code", { text: "publish.js" });
        //             f.createSpan({ text: "file." });
        //             f.createEl("br");
        //             f.createEl("strong", {
        //                 text: "Please note that this can only be done on custom domain publish sites."
        //             });
        //         })
        //     )
        // .addButton((b) => {
        //     b.setButtonText("Generate");
        //     b.onClick((evt) => {
        //         const admonition_icons: {
        //             [admonition_type: string]: {
        //                 icon: string;
        //                 color: string;
        //             };
        //         } = {};
        //
        //         for (let key in this.plugin.admonitions) {
        //             const value = this.plugin.admonitions[key];
        //
        //             admonition_icons[key] = {
        //                 icon:
        //                     this.plugin.iconManager.getIconNode(value.icon)
        //                         ?.outerHTML ?? "",
        //                 color: value.color
        //             };
        //         }
        //
        //         const js = CONTENT.replace(
        //             /ADMONITION_ICON_MAP\s?=\s?\{\}/,
        //             "ADMONITION_ICON_MAP=" +
        //                 JSON.stringify(admonition_icons)
        //         );
        //         const file = new Blob([js], {
        //             type: "text/javascript"
        //         });
        //         const link = createEl("a", {
        //             href: URL.createObjectURL(file),
        //             attr: {
        //                 download: "publish.admonition.js"
        //             }
        //         });
        //         link.click();
        //         link.detach();
        //     });
        // });
    }

    buildTypes() {
        this.additionalEl.empty();
        for (const admonition of Object.values(
            this.plugin.data.userAdmonitions
        )) {
            let setting = new Setting(this.additionalEl);

            let admonitionElement = this.plugin.getAdmonitionElement(
                admonition.type,
                admonition.type[0].toUpperCase() +
                    admonition.type.slice(1).toLowerCase(),
                admonition.icon,
                admonition.injectColor ?? this.plugin.data.injectColor
                    ? admonition.color
                    : null
            );
            setting.infoEl.replaceWith(admonitionElement);

            if (!admonition.command) {
                setting.addExtraButton((b) => {
                    b.setIcon(ADD_COMMAND_NAME.toString())
                        .setTooltip(t("Register Commands"))
                        .onClick(async () => {
                            this.plugin.registerCommandsFor(admonition);
                            await this.plugin.saveSettings();
                            this.display();
                        });
                });
            } else {
                setting.addExtraButton((b) => {
                    b.setIcon(REMOVE_COMMAND_NAME.toString())
                        .setTooltip(t("Unregister Commands"))
                        .onClick(async () => {
                            this.plugin.unregisterCommandsFor(admonition);
                            await this.plugin.saveSettings();
                            this.display();
                        });
                });
            }

            setting
                .addExtraButton((b) => {
                    b.setIcon("pencil")
                        .setTooltip(t("Edit"))
                        .onClick(() => {
                            let modal = new SettingsModal(
                                this.plugin,
                                admonition
                            );

                            modal.onClose = async () => {
                                if (modal.saved) {
                                    const hasCommand = admonition.command;
                                    const modalAdmonition = {
                                        type: modal.type,
                                        color: modal.color,
                                        icon: modal.icon,
                                        command: hasCommand,
                                        title: modal.title,
                                        injectColor: modal.injectColor,
                                        noTitle: modal.noTitle,
                                        copy: modal.copy
                                    };

                                    if (
                                        modalAdmonition.type != admonition.type
                                    ) {
                                        this.plugin.unregisterType(admonition);

                                        const existing: [string, Admonition][] =
                                            Object.entries(
                                                this.plugin.data.userAdmonitions
                                            );

                                        this.plugin.data.userAdmonitions =
                                            Object.fromEntries(
                                                existing.map(([type, def]) => {
                                                    if (
                                                        type == admonition.type
                                                    ) {
                                                        return [
                                                            modalAdmonition.type,
                                                            modalAdmonition
                                                        ];
                                                    }
                                                    return [type, def];
                                                })
                                            );
                                    } else {
                                        this.plugin.data.userAdmonitions[
                                            modalAdmonition.type
                                        ] = modalAdmonition;
                                    }

                                    this.plugin.registerType(
                                        modalAdmonition.type
                                    );

                                    this.plugin.calloutManager.addAdmonition(
                                        modalAdmonition
                                    );

                                    this.display();
                                }
                            };

                            modal.open();
                        });
                })
                .addExtraButton((b) => {
                    b.setIcon("trash")
                        .setTooltip(t("Delete"))
                        .onClick(() => {
                            this.plugin.removeAdmonition(admonition);
                            this.display();
                        });
                });
        }
    }
}

class SettingsModal extends Modal {
    color: string = "#7d7d7d";
    icon: AdmonitionIconDefinition = {};
    type: string;
    saved: boolean = false;
    error: boolean = false;
    title: string;
    injectColor: boolean = this.plugin.data.injectColor;
    noTitle: boolean = false;
    admonitionPreviewParent: HTMLElement;
    admonitionPreview: HTMLElement;
    copy: boolean;
    originalType: string;
    editing = false;
    constructor(public plugin: ObsidianAdmonition, admonition?: Admonition) {
        super(plugin.app);
        if (admonition) {
            this.editing = true;
            this.color = admonition.color;
            this.icon = admonition.icon;
            this.type = admonition.type;
            this.originalType = admonition.type;
            this.title = admonition.title;
            this.injectColor = admonition.injectColor ?? this.injectColor;
            this.noTitle = admonition.noTitle ?? false;
            this.copy = admonition.copy ?? this.plugin.data.copyButton;
        }
    }

    setAdmonitionElement(title: string) {
        this.admonitionPreviewParent.empty();
        this.admonitionPreview = this.plugin.getAdmonitionElement(
            this.type,
            title[0].toUpperCase() + title.slice(1).toLowerCase(),
            this.icon,
            this.injectColor ?? this.plugin.data.injectColor ? this.color : null
        );
        this.admonitionPreview
            .createDiv("callout-content admonition-content")
            .createEl("p", {
                text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod nulla."
            });
        this.admonitionPreviewParent.appendChild(this.admonitionPreview);
    }
    async display() {
        this.containerEl.addClass("admonition-settings-modal");
        this.titleEl.setText(`${this.editing ? "Edit" : "Add"} Admonition`);
        let { contentEl } = this;

        contentEl.empty();

        const settingDiv = contentEl.createDiv();
        const title = this.title ?? this.type ?? "...";

        this.admonitionPreviewParent = contentEl.createDiv();
        this.setAdmonitionElement(
            title[0].toUpperCase() + title.slice(1).toLowerCase()
        );

        let typeText: TextComponent;
        const typeSetting = new Setting(settingDiv)
            .setName(t("Admonition Type"))
            .addText((text) => {
                typeText = text;
                typeText.setValue(this.type).onChange((v) => {
                    const valid = AdmonitionValidator.validateType(
                        v,
                        this.plugin,
                        this.originalType
                    );
                    if (valid.success == false) {
                        SettingsModal.setValidationError(
                            text.inputEl,
                            valid.message
                        );
                        return;
                    }

                    SettingsModal.removeValidationError(text.inputEl);

                    this.type = v;
                    if (!this.title)
                        this.setAdmonitionElement(
                            this.type?.[0].toUpperCase() +
                                this.type?.slice(1).toLowerCase()
                        );
                });
            });
        typeSetting.controlEl.addClass("admonition-type-setting");

        typeSetting.descEl.createSpan({
            text: "This is used to create the admonition (e.g.,  "
        });
        typeSetting.descEl.createEl("code", {
            text: "note"
        });
        typeSetting.descEl.createSpan({
            text: " or "
        });
        typeSetting.descEl.createEl("code", {
            text: "abstract"
        });
        typeSetting.descEl.createSpan({
            text: ")"
        });

        new Setting(settingDiv)
            .setName(t("Admonition Title"))
            .setDesc(
                t("This will be the default title for this admonition type.")
            )
            .addText((text) => {
                text.setValue(this.title).onChange((v) => {
                    if (!v.length) {
                        this.title = null;
                        this.setAdmonitionElement(
                            this.type?.[0].toUpperCase() +
                                title.slice(1).toLowerCase()
                        );
                        return;
                    }

                    this.title = v;
                    this.setAdmonitionElement(this.title);
                });
            });
        new Setting(settingDiv)
            .setName(t("No Admonition Title by Default"))
            .setDesc(
                createFragment((e) => {
                    e.createSpan({
                        text: t("The admonition will have no title unless ")
                    });
                    e.createEl("code", { text: "title" });
                    e.createSpan({ text: t(" is explicitly provided.") });
                })
            )
            .addToggle((t) => {
                t.setValue(this.noTitle).onChange((v) => (this.noTitle = v));
            });
        new Setting(settingDiv)
            .setName(t("Show Copy Button"))
            .setDesc(
                createFragment((e) => {
                    e.createSpan({
                        text: "A copy button will be added to the admonition & callout."
                    });
                })
            )
            .addToggle((t) => {
                t.setValue(this.copy).onChange((v) => (this.copy = v));
            });

        const input = createEl("input", {
            attr: {
                type: "file",
                name: "image",
                accept: "image/*"
            }
        });
        let iconText: TextComponent;
        new Setting(settingDiv)
            .setName(t("Admonition Icon"))
            .setDesc("Icon to display next to the title.")
            .addText((text) => {
                iconText = text;
                if (this.icon.type !== "image") text.setValue(this.icon.name);

                const validate = async () => {
                    const v = text.inputEl.value;
                    const valid = AdmonitionValidator.validateIcon(
                        { name: v },
                        this.plugin
                    );
                    if (valid.success == false) {
                        SettingsModal.setValidationError(
                            text.inputEl,
                            valid.message
                        );
                        return;
                    }

                    SettingsModal.removeValidationError(text.inputEl);
                    const ic = this.plugin.iconManager.getIconType(v);
                    this.icon = {
                        name: v as AdmonitionIconName,
                        type: ic as AdmonitionIconType
                    };

                    let iconEl = this.admonitionPreview.querySelector(
                        ".admonition-title-icon"
                    );

                    iconEl.innerHTML =
                        this.plugin.iconManager.getIconNode(this.icon)
                            ?.outerHTML ?? "";
                };

                const modal = new IconSuggestionModal(
                    this.plugin,
                    text,
                    this.plugin.iconManager.iconDefinitions
                );

                modal.onSelect((item) => {
                    text.inputEl.value = item.item.name;
                    validate();
                    modal.close();
                });

                text.inputEl.onblur = validate;
            })
            .addButton((b) => {
                b.setButtonText(t("Upload Image")).setIcon("image-file");
                b.buttonEl.addClass("admonition-file-upload");
                b.buttonEl.appendChild(input);
                b.onClick(() => input.click());
            });

        /** Image Uploader */
        input.onchange = async () => {
            const { files } = input;

            if (!files.length) return;

            const image = files[0];
            const reader = new FileReader();
            reader.onloadend = (evt) => {
                const image = new Image();
                image.onload = () => {
                    try {
                        // Resize the image
                        const canvas = document.createElement("canvas"),
                            max_size = 24;
                        let width = image.width,
                            height = image.height;
                        if (width > height) {
                            if (width > max_size) {
                                height *= max_size / width;
                                width = max_size;
                            }
                        } else {
                            if (height > max_size) {
                                width *= max_size / height;
                                height = max_size;
                            }
                        }
                        canvas.width = width;
                        canvas.height = height;
                        canvas
                            .getContext("2d")
                            .drawImage(image, 0, 0, width, height);

                        this.icon = {
                            name: canvas.toDataURL("image/png"),
                            type: "image"
                        };
                        this.display();
                    } catch (e) {
                        new Notice("There was an error parsing the image.");
                    }
                };
                image.src = evt.target.result.toString();
            };
            reader.readAsDataURL(image);

            input.value = null;
        };

        const color = settingDiv.createDiv("admonition-color-settings");
        this.createColor(color);

        let footerEl = contentEl.createDiv();
        let footerButtons = new Setting(footerEl);
        footerButtons.addButton((b) => {
            b.setTooltip(t("Save"))
                .setIcon("checkmark")
                .onClick(async () => {
                    const icon = { ...this.icon };
                    if (iconText.inputEl.value?.length) {
                        icon.name = iconText.inputEl.value;
                    }
                    const valid = AdmonitionValidator.validate(
                        this.plugin,
                        typeText.inputEl.value,
                        icon,
                        this.originalType
                    );
                    if (valid.success == false) {
                        SettingsModal.setValidationError(
                            valid.failed == "type"
                                ? typeText.inputEl
                                : iconText.inputEl,
                            valid.message
                        );
                        new Notice("Fix errors before saving.");
                        return;
                    }
                    this.saved = true;
                    this.close();
                });
            return b;
        });
        footerButtons.addExtraButton((b) => {
            b.setIcon("cross")
                .setTooltip("Cancel")
                .onClick(() => {
                    this.saved = false;
                    this.close();
                });
            return b;
        });
    }
    createColor(el: HTMLDivElement) {
        el.empty();
        const desc = this.injectColor
            ? "Set the admonition color. Disable to set manually using CSS."
            : "Admonition color is disabled and must be manually set using CSS.";
        new Setting(el)
            .setName(t("Color"))
            .setDesc(desc)
            .addText((t) => {
                t.inputEl.setAttribute("type", "color");

                if (!this.injectColor) {
                    t.inputEl.setAttribute("disabled", "true");
                }

                t.setValue(rgbToHex(this.color)).onChange((v) => {
                    let color = hexToRgb(v);
                    if (!color) return;
                    this.color = `${color.r}, ${color.g}, ${color.b}`;
                    this.admonitionPreview.setAttribute(
                        "style",
                        `--callout-color: ${this.color};`
                    );
                });
            })
            .addToggle((t) =>
                t
                    .setValue(this.injectColor)
                    .setTooltip(
                        `${
                            this.injectColor ? "Disable" : "Enable"
                        } Admonition Color`
                    )
                    .onChange((v) => {
                        this.injectColor = v;

                        if (!v) {
                            this.admonitionPreview.removeAttribute("style");
                        } else {
                            this.admonitionPreview.setAttribute(
                                "style",
                                `--callout-color: ${this.color};`
                            );
                        }

                        this.createColor(el);
                    })
            );
    }

    onOpen() {
        this.display();
    }

    static setValidationError(textInput: HTMLInputElement, message?: string) {
        textInput.addClass("is-invalid");
        if (message) {
            textInput.parentElement.addClasses([
                "has-invalid-message",
                "unset-align-items"
            ]);
            textInput.parentElement.parentElement.addClass(
                ".unset-align-items"
            );
            let mDiv = textInput.parentElement.querySelector(
                ".invalid-feedback"
            ) as HTMLDivElement;

            if (!mDiv) {
                mDiv = textInput.parentElement.createDiv({
                    cls: "invalid-feedback"
                });
            } else {
            }
            mDiv.setText(message);
        }
    }
    static removeValidationError(textInput: HTMLInputElement) {
        textInput.removeClass("is-invalid");
        textInput.parentElement.removeClasses([
            "has-invalid-message",
            "unset-align-items"
        ]);
        textInput.parentElement.parentElement.removeClass(".unset-align-items");

        if (textInput.parentElement.querySelector(".invalid-feedback")) {
            textInput.parentElement.removeChild(
                textInput.parentElement.querySelector(".invalid-feedback")
            );
        }
    }
}

function hexToRgb(hex: string) {
    let result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);

    return result
        ? {
              r: parseInt(result[1], 16),
              g: parseInt(result[2], 16),
              b: parseInt(result[3], 16)
          }
        : null;
}
function componentToHex(c: number) {
    var hex = c.toString(16);
    return hex.length == 1 ? "0" + hex : hex;
}
function rgbToHex(rgb: string) {
    let result = /^(\d+),\s?(\d+),\s?(\d+)/i.exec(rgb);
    if (!result || !result.length) {
        return "";
    }
    return `#${componentToHex(Number(result[1]))}${componentToHex(
        Number(result[2])
    )}${componentToHex(Number(result[3]))}`;
}
